// ============================================================================
//
// Graph framework by Aurelien Moreau aurelien.moreau@gmail.com
// Modified by Pierre CHATEL chatelp@gmail.com
// Distributed under LGPL
// Copyright (C) Aurelien Moreau, 2008, 2009. All rights reserved
//
// ============================================================================

package com.thalesgroup.graphFramework.core.utils;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import com.thalesgroup.graphFramework.core.Edge;
import com.thalesgroup.graphFramework.core.Graph;
import com.thalesgroup.graphFramework.core.GraphBuilder;
import com.thalesgroup.graphFramework.core.Vertex;
import com.thalesgroup.graphFramework.core.VerticesPartition;
import com.thalesgroup.graphFramework.core.impl.GraphBuilderImpl;

import com.thalesgroup.graphFramework.collections.MapArrayList;
import com.thalesgroup.graphFramework.collections.MapList;

public class GraphModulizer<VTgt, ETgt, VSrc, ESrc> {

	public static interface Composer<Tgt, Src> {
		public Tgt compose(List<Src> srcs);
	}

	protected GraphBuilder<VTgt, ETgt> builder;

	protected Composer<VTgt, VSrc> vertexComposer;

	protected Composer<ETgt, ESrc> edgeComposer;

	public GraphModulizer(final GraphBuilder<VTgt, ETgt> builder,
			final Composer<VTgt, VSrc> vertexComposer,
			final Composer<ETgt, ESrc> edgeComposer) {
		this.builder = builder;
		this.vertexComposer = vertexComposer;
		this.edgeComposer = edgeComposer;
	}

	public GraphModulizer(final Composer<VTgt, VSrc> vertexComposer,
			final Composer<ETgt, ESrc> edgeComposer) {
		this(new GraphBuilderImpl<VTgt, ETgt>(), vertexComposer, edgeComposer);
	}

	@SuppressWarnings("unchecked")
	public Graph<VTgt, ETgt> modulize(
			final VerticesPartition<VSrc, ESrc> partition) {

		builder.createNewGraph();
		final int size = partition.getGraph().getVerticesSize();

		// L'entier representant le cluster va nous servir a indexer les
		// tableaux, toutes les cases ne seront pas utilisees.
		final Vertex<VTgt, ETgt>[] clusters = new Vertex[size];

		{ // Calcul des noeuds
			final ArrayList<Vertex<VSrc, ESrc>>[] preClusters = new ArrayList[size];
			for(Iterator<Vertex<VSrc,ESrc>> iv=partition.getGraph().getVertices(); iv.hasNext();) {
				Vertex<VSrc,ESrc> v=iv.next();
				final int c = partition.findLeader(v).getIndex();
				// int c = dSet.findSet(v.getIndex());
				if (preClusters[c] == null) {
					preClusters[c] = new ArrayList<Vertex<VSrc, ESrc>>();
				}
				preClusters[c].add(v);
			}

			for (int c = 0; c < size; c++) {
				if (preClusters[c] != null) {
					final ArrayList<VSrc> module = new ArrayList<VSrc>(
							preClusters[c].size());
					for (int v = 0; v < preClusters[c].size(); v++) {
						module.add(preClusters[c].get(v).getVertex());
					}
					final VTgt tgtData = vertexComposer.compose(module);
					clusters[c] = builder.createVertex(tgtData);
				}
			}
		}

		{// Calcul des arretes
			final MapList<Integer, ESrc>[] outputClusters = new MapList[size];
			for(Iterator<Vertex<VSrc, ESrc>> iv=partition.getGraph().getVertices(); iv.hasNext();) {
				Vertex<VSrc,ESrc> v=iv.next();
				final int from = partition.findLeader(v).getIndex();
				if (outputClusters[from] == null) {
					outputClusters[from] = new MapArrayList<Integer, ESrc>();
				}
				for(Iterator<Edge<VSrc, ESrc>> ie=v.getOuts(); ie.hasNext();) {
					Edge<VSrc, ESrc> e=ie.next();
					final int to = partition.findLeader(e.getOut()).getIndex();
					if (from != to) {
						outputClusters[from].add(to, e.getEdge());
					}
				}
			}

			for (int c = 0; c < size; c++) {
				if (clusters[c] != null) {
					final Vertex<VTgt, ETgt> from = clusters[c];
					for (final Map.Entry<Integer, List<ESrc>> e : outputClusters[c]
							.entrySet()) {
						final Vertex<VTgt, ETgt> to = clusters[e.getKey()];
						final ETgt tgtData = edgeComposer.compose(e.getValue());
						builder.createEdge(from, to, tgtData);
					}
				}
			}
		}

		builder.finalizeGraph();
		return builder.getCurrentGraph();

	}
}
